From 54e02efe96ea8d60978353237fdb2156b876b3cb Mon Sep 17 00:00:00 2001 From: tsteven4 <13596209+tsteven4@users.noreply.github.com> Date: Thu, 26 Jan 2023 07:53:14 -0700 Subject: [PATCH] convert igc to Format class. (#996) and fix a few bugs. --- CMakeLists.txt | 1 + igc.cc | 270 +++++++++++++++++++------------------------------ igc.h | 164 ++++++++++++++++++++++++++++++ vecs.cc | 4 +- 4 files changed, 269 insertions(+), 170 deletions(-) create mode 100644 igc.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 0d4921501..d65981698 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -233,6 +233,7 @@ set(HEADERS inifile.h kml.h legacyformat.h + igc.h lowranceusr.h navilink.h nmea.h diff --git a/igc.cc b/igc.cc index 4e7d716e9..2825d6fd6 100644 --- a/igc.cc +++ b/igc.cc @@ -23,22 +23,25 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ +#include "igc.h" + #include // for assert #include // for fabs, lround -#include // for sscanf, snprintf, fputs, printf, stdout, putchar, size_t +#include // for sscanf, printf, snprintf, size_t #include // for labs, ldiv, ldiv_t, abs #include // for strcmp, strlen, strtok, strcat, strchr, strcpy, strncat #include // for reverse_iterator, operator==, prev, next #include // for optional #include // for QByteArray +#include // for QChar +#include // for QLatin1Char #include // for QDate #include // for QDateTime #include // for QList<>::const_iterator #include // for QString, operator+, QStringLiteral #include // for QStringList #include // for operator<, operator==, QTime -#include // for QVector #include // for UTC, SkipEmptyParts #include // for foreach, qPrintable @@ -48,45 +51,11 @@ #include "src/core/datetime.h" // for DateTime -static gbfile* file_in, *file_out; -static char manufacturer[4]; -static const route_head* head; -static char* timeadj = nullptr; -static int lineno; - #define MYNAME "IGC" -#define MAXRECLEN 79 // Includes null terminator and CR/LF -#define MAXDESCLEN 1024 -#define PRESTRKNAME "PRESALTTRK" -#define GNSSTRKNAME "GNSSALTTRK" #define HDRMAGIC "IGCHDRS" #define HDRDELIM "~" #define DATEMAGIC "IGCDATE" -/* - * IGC record types. - * These appear as the first char in each record. - */ -enum igc_rec_type_t { - rec_manuf_id = 'A', // FR manufacturer and identification - rec_fix = 'B', // Fix - rec_task = 'C', // Task/declaration - rec_diff_gps = 'D', // Differential GPS - rec_event = 'E', // Event - rec_constel = 'F', // Constellation - rec_security = 'G', // Security - rec_header = 'H', // File header - rec_fix_defn = 'I', // List of extension data included at end of each fix (B) record - rec_extn_defn = 'J', // List of data included in each extension (K) record - rec_extn_data = 'K', // Extension data - rec_log_book = 'L', // Logbook/comments - - // M..Z are spare - - rec_none = 0, // No record - rec_bad = 1, // Bad record -}; - /* * See if two lat/lon pairs are approximately equal. * @param lat1 The latitude of coordinate pair 1 @@ -96,7 +65,7 @@ enum igc_rec_type_t { * @retval 1 The coordinates are approximately equal * @retval 0 The coordinates are significantly different */ -static unsigned char coords_match(double lat1, double lon1, double lat2, double lon2) +unsigned char IgcFormat::coords_match(double lat1, double lon1, double lat2, double lon2) { return (fabs(lat1 - lat2) < 0.0001 && fabs(lon1 - lon2) < 0.0001) ? 1 : 0; } @@ -107,10 +76,10 @@ static unsigned char coords_match(double lat1, double lon1, double lat2, double /* * Get an IGC record from the input file - * @param rec Caller allocated storage for the record. At least MAXRECLEN chars must be allocated. + * @param rec Caller allocated storage for the record. At least kMaxRecLen chars must be allocated. * @return the record type. rec_none on EOF, rec_bad on fgets() or parse error. */ -static igc_rec_type_t get_record(char** rec) +IgcFormat::igc_rec_type_t IgcFormat::get_record(char** rec) { char* c; retry: @@ -133,62 +102,39 @@ retry: return (igc_rec_type_t) c[0]; } -static void rd_init(const QString& fname) +void IgcFormat::rd_init(const QString& fname) { char* ibuf; file_in = gbfopen(fname, "r", MYNAME); - lineno = 0; // File must begin with a manufacturer/ID record if (get_record(&ibuf) != rec_manuf_id || sscanf(ibuf, "A%3[A-Z]", manufacturer) != 1) { fatal(MYNAME ": %s is not an IGC file\n", qPrintable(fname)); } } -static void rd_deinit() +void IgcFormat::rd_deinit() { gbfclose(file_in); } -namespace { // fix ODR violation with brauniger_iq - enum state_t { id, takeoff, start, turnpoint, finish, landing }; -} -inline state_t& operator++(state_t& s) // prefix -{ - return s = static_cast(s + 1); -} -#if 0 // No callers. -inline state_t operator++(state_t& s, int) // postfix -{ - state_t ret(s); - ++s; - return ret; -} -#endif - /** * Handle pre- or post-flight task declarations. * A route is created for each set of waypoints in a task declaration. * @param rec A single task record */ -static void igc_task_rec(const char* rec) +void IgcFormat::TaskRecordReader::igc_task_rec(const char* rec) { - static unsigned int num_tp, tp_ct; - static route_head* rte_head; - static QDateTime creation; - - char task_num[5]; unsigned int lat_deg, lat_min, lat_frac; unsigned int lon_deg, lon_min, lon_frac; char lat_hemi[2], lon_hemi[2]; - char short_name[8]; - char tmp_str[MAXRECLEN]; - static state_t state = id; + char tmp_str[kMaxRecLen]; // First task record identifies the task to follow - if (id == state) { - static char flight_date[7]; - char task_desc[MAXRECLEN]; + if (state_t::id == state) { + char task_num[5]; + char flight_date[7]; + char task_desc[kMaxRecLen]; task_desc[0] = '\0'; int day; int month; @@ -218,7 +164,7 @@ static void igc_task_rec(const char* rec) rte_head->rte_name = task_num; rte_head->rte_desc = QStringLiteral(DATEMAGIC) + flight_date + QStringLiteral(": ") + task_desc; route_add_head(rte_head); - ++state; + state = state_t::takeoff; return; } // Get the waypoint @@ -241,32 +187,32 @@ static void igc_task_rec(const char* rec) // Name the waypoint according to the order of the task record switch (state) { - case takeoff: - snprintf(short_name, 8, "TAKEOFF"); - ++state; + case state_t::takeoff: + wpt->shortname = "TAKEOFF"; + state = state_t::start; break; - case start: - snprintf(short_name, 8, "START"); + case state_t::start: + wpt->shortname = "START"; tp_ct = 0; - ++state; + state = state_t::turnpoint; break; - case turnpoint: + case state_t::turnpoint: if (++tp_ct == num_tp) { - ++state; + state = state_t::finish; } - snprintf(short_name, 8, "TURN%02u", tp_ct); + wpt->shortname = QStringLiteral("TURN%1").arg(tp_ct, 2, 10, QLatin1Char('0')); break; - case finish: - snprintf(short_name, 8, "FINISH"); - ++state; + case state_t::finish: + wpt->shortname = "FINISH"; + state = state_t::landing; break; - case landing: - snprintf(short_name, 8, "LANDING"); - state = id; + case state_t::landing: + wpt->shortname = "LANDING"; + state = state_t::id; break; default: @@ -279,11 +225,10 @@ static void igc_task_rec(const char* rec) delete wpt; return; } - wpt->shortname = short_name; route_add_wpt(rte_head, wpt); } -static void data_read() +void IgcFormat::read() { char* ibuf; int hours, mins, secs; @@ -303,8 +248,8 @@ static void data_read() QTime tod; char tmp_str[20]; char* hdr_data; - size_t remain; - char trk_desc[MAXDESCLEN + 1]; + char trk_desc[kMaxDescLen + 1]; + TaskRecordReader task_record_reader; strcpy(trk_desc, HDRMAGIC HDRDELIM); @@ -348,10 +293,10 @@ static void data_read() } } else { // Store other header data in the track descriptions - if (strlen(trk_desc) < MAXDESCLEN) { - remain = MAXDESCLEN - strlen(trk_desc); + if (strlen(trk_desc) < kMaxDescLen) { + size_t remain = kMaxDescLen - strlen(trk_desc); strncat(trk_desc, ibuf, remain); - remain = MAXDESCLEN - strlen(trk_desc); + remain = kMaxDescLen - strlen(trk_desc); strncat(trk_desc, HDRDELIM, remain); } } @@ -365,14 +310,14 @@ static void data_read() // Create a track for pressure altitude waypoints if (!pres_head) { pres_head = new route_head; - pres_head->rte_name = PRESTRKNAME; + pres_head->rte_name = kPresTrkName; pres_head->rte_desc = trk_desc; track_add_head(pres_head); } // Create a second track for GNSS altitude waypoints if (!gnss_head) { gnss_head = new route_head; - gnss_head->rte_name = GNSSTRKNAME; + gnss_head->rte_name = kGNSSTrkName; gnss_head->rte_desc = trk_desc; track_add_head(gnss_head); } @@ -429,7 +374,7 @@ static void data_read() case rec_task: // Create a route for each pre-flight declaration - igc_task_rec(ibuf); + task_record_reader.igc_task_rec(ibuf); break; case rec_log_book: @@ -440,22 +385,18 @@ static void data_read() if (strcmp(tmp_str, "PFC") == 0) { // Create a route for each post-flight declaration - igc_task_rec(ibuf + 4); + task_record_reader.igc_task_rec(ibuf + 4); break; } else if (global_opts.debug_level) { if (strcmp(tmp_str, "OOI") == 0) { - fputs(MYNAME ": Observer Input> ", stdout); + printf(MYNAME ": Observer Input> %s\n", ibuf + 4); } else if (strcmp(tmp_str, "PLT") == 0) { - fputs(MYNAME ": Pilot Input> ", stdout); + printf(MYNAME ": Pilot Input> %s\n", ibuf + 4); } else if (strcmp(tmp_str, manufacturer) == 0) { - fputs(MYNAME ": Manufacturer Input> ", stdout); + printf(MYNAME ": Manufacturer Input> %s\n", ibuf + 4); } else { - fputs(MYNAME ": Anonymous Input> ", stdout); - fputs(ibuf + 1, stdout); - break; + printf(MYNAME ": Anonymous Input> %s\n", ibuf + 1); } - fputs(ibuf + 4, stdout); - putchar('\n'); } break; @@ -501,32 +442,30 @@ static void data_read() * Callbacks used to scan for specific track types */ -static void detect_pres_track(const route_head* rh) +void IgcFormat::detect_pres_track(const route_head* rh) { - if (rh->rte_name.startsWith(PRESTRKNAME)) { + if (rh->rte_name.startsWith(kPresTrkName)) { head = rh; } } -static void detect_gnss_track(const route_head* rh) +void IgcFormat::detect_gnss_track(const route_head* rh) { - if (rh->rte_name.startsWith(GNSSTRKNAME)) { + if (rh->rte_name.startsWith(kGNSSTrkName)) { head = rh; } } -static void detect_other_track(const route_head* rh) +void IgcFormat::detect_other_track(const route_head* rh, int& max_waypt_ct) { - static int max_waypt_ct; - if (!head) { max_waypt_ct = 0; } // Find other track with the most waypoints if (rh->rte_waypt_ct() > max_waypt_ct && (rh->rte_name.isEmpty() || - (!rh->rte_name.startsWith(PRESTRKNAME) && - !rh->rte_name.startsWith(GNSSTRKNAME)))) { + (!rh->rte_name.startsWith(kPresTrkName) && + !rh->rte_name.startsWith(kGNSSTrkName)))) { head = rh; max_waypt_ct = rh->rte_waypt_ct(); } @@ -539,18 +478,28 @@ static void detect_other_track(const route_head* rh) * @param gnss_track Set by the function to the GNSS altitude track * head. NULL if not found. */ -static void get_tracks(const route_head** pres_track, const route_head** gnss_track) +void IgcFormat::get_tracks(const route_head** pres_track, const route_head** gnss_track) { head = nullptr; - track_disp_all(detect_pres_track, nullptr, nullptr); + auto detect_pres_track_lambda = [this](const route_head* rte)->void { + detect_pres_track(rte); + }; + track_disp_all(detect_pres_track_lambda, nullptr, nullptr); *pres_track = head; head = nullptr; - track_disp_all(detect_gnss_track, nullptr, nullptr); + auto detect_gnss_track_lambda = [this](const route_head* rte)->void { + detect_gnss_track(rte); + }; + track_disp_all(detect_gnss_track_lambda, nullptr, nullptr); *gnss_track = head; head = nullptr; - track_disp_all(detect_other_track, nullptr, nullptr); + int max_waypt_ct{}; + auto detect_other_track_lambda = [this, &max_waypt_ct](const route_head* rte)->void { + detect_other_track(rte, max_waypt_ct); + }; + track_disp_all(detect_other_track_lambda, nullptr, nullptr); if (!*pres_track && *gnss_track && head) { *pres_track = head; @@ -565,9 +514,8 @@ static void get_tracks(const route_head** pres_track, const route_head** gnss_tr * IGC string formatting functions */ -static char* latlon2str(const Waypoint* wpt) +QByteArray IgcFormat::latlon2str(const Waypoint* wpt) { - static char str[18] = ""; // We use lround here because it: // "Returns the integral value that is nearest to x, with halfway cases rounded away from zero." // The halfway rounding cases of *printf are not precisely defined, and can vary with implementation. @@ -581,14 +529,15 @@ static char* latlon2str(const Waypoint* wpt) ldiv_t lat_digits = ldiv(labs(lat_milliminutes), 60000L); ldiv_t lon_digits = ldiv(labs(lon_milliminutes), 60000L); - if (snprintf(str, 18, "%02ld%05ld%c%03ld%05ld%c", + char str[18]; + if (snprintf(str, sizeof(str), "%02ld%05ld%c%03ld%05ld%c", lat_digits.quot, lat_digits.rem, lat_hemi, lon_digits.quot, lon_digits.rem, lon_hemi) != 17) { fatal(MYNAME ": Bad waypoint format '%s'\n", str); } return str; } -static QByteArray date2str(const gpsbabel::DateTime& dt) +QByteArray IgcFormat::date2str(const gpsbabel::DateTime& dt) const { QByteArray str = dt.toUTC().toString("ddMMyy").toUtf8(); if (str.size() != 6) { @@ -597,7 +546,7 @@ static QByteArray date2str(const gpsbabel::DateTime& dt) return str; } -static QByteArray tod2str(const gpsbabel::DateTime& tod) +QByteArray IgcFormat::tod2str(const gpsbabel::DateTime& tod) const { QByteArray str = tod.toUTC().toString("hhmmss").toUtf8(); if (str.size() != 6) { @@ -609,7 +558,7 @@ static QByteArray tod2str(const gpsbabel::DateTime& tod) /* * Write header records */ -static void wr_header() +void IgcFormat::wr_header() { const route_head* pres_track; const route_head* track; @@ -667,19 +616,18 @@ static void wr_header() * Generation of IGC task declaration records */ -static void wr_task_wpt_name(const Waypoint* wpt, const char* alt_name) +void IgcFormat::wr_task_wpt_name(const Waypoint* wpt, const char* alt_name) { - gbfprintf(file_out, "C%s%s\r\n", latlon2str(wpt), + gbfprintf(file_out, "C%s%s\r\n", latlon2str(wpt).constData(), !wpt->description.isEmpty() ? CSTR(wpt->description) : !wpt->shortname.isEmpty() ? CSTR(wpt->shortname) : alt_name); } -static void wr_task_hdr(const route_head* rte) +void IgcFormat::wr_task_hdr(const route_head* rte, unsigned int task_num) { unsigned char have_takeoff = 0; char flight_date[7] = "000000"; - char task_desc[MAXRECLEN] = ""; + char task_desc[kMaxRecLen] = ""; int num_tps = rte->rte_waypt_ct() - 2; - static unsigned int task_num = 1; if (num_tps < 0) { fatal(MYNAME ": Empty task route\n"); @@ -712,7 +660,7 @@ static void wr_task_hdr(const route_head* rte) } gbfprintf(file_out, "C%s%s%s%04u%02u%s\r\n", date2str(rte_time).constData(), - tod2str(rte_time).constData(), flight_date, task_num++, num_tps, task_desc); + tod2str(rte_time).constData(), flight_date, task_num, num_tps, task_desc); if (!have_takeoff) { // Generate the takeoff waypoint @@ -720,12 +668,12 @@ static void wr_task_hdr(const route_head* rte) } } -static void wr_task_wpt(const Waypoint* wpt) +void IgcFormat::wr_task_wpt(const Waypoint* wpt) { wr_task_wpt_name(wpt, ""); } -static void wr_task_tlr(const route_head* rte) +void IgcFormat::wr_task_tlr(const route_head* rte) { // If the landing waypoint is not supplied we need to generate it. const Waypoint* wpt = rte->waypoint_list.back(); @@ -736,15 +684,25 @@ static void wr_task_tlr(const route_head* rte) } } -static void wr_tasks() +void IgcFormat::wr_tasks() { - route_disp_all(wr_task_hdr, wr_task_tlr, wr_task_wpt); + unsigned int task_num = 1; + auto wr_task_hdr_lambda = [this, &task_num](const route_head* rte)->void { + wr_task_hdr(rte, task_num++); + }; + auto wr_task_tlr_lambda = [this](const route_head* rte)->void { + wr_task_tlr(rte); + }; + auto wr_task_wpt_lambda = [this](const Waypoint* waypointp)->void { + wr_task_wpt(waypointp); + }; + route_disp_all(wr_task_hdr_lambda, wr_task_tlr_lambda, wr_task_wpt_lambda); } /* * Write a single fix record */ -static void wr_fix_record(const Waypoint* wpt, int pres_alt, int gnss_alt) +void IgcFormat::wr_fix_record(const Waypoint* wpt, int pres_alt, int gnss_alt) { gpsbabel::DateTime tt = wpt->GetCreationTime(); if (!tt.isValid()) { @@ -758,7 +716,7 @@ static void wr_fix_record(const Waypoint* wpt, int pres_alt, int gnss_alt) gnss_alt = 0; } gbfprintf(file_out, "B%s%sA%05d%05d\r\n", tod2str(tt).constData(), - latlon2str(wpt), pres_alt, gnss_alt); + latlon2str(wpt).constData(), pres_alt, gnss_alt); } /** @@ -769,7 +727,7 @@ static void wr_fix_record(const Waypoint* wpt, int pres_alt, int gnss_alt) * @return The number of seconds to add to the GNSS track in order to align * it with the pressure track. */ -static int correlate_tracks(const route_head* pres_track, const route_head* gnss_track) +int IgcFormat::correlate_tracks(const route_head* pres_track, const route_head* gnss_track) const { double alt_diff; double speed; @@ -833,10 +791,8 @@ static int correlate_tracks(const route_head* pres_track, const route_head* gnss * @param time The time that we are interested in. * @return The altitude interpolated from the track. */ -static double interpolate_alt(const route_head* track, const gpsbabel::DateTime& time) +double IgcFormat::Interpolater::interpolate_alt(const route_head* track, const gpsbabel::DateTime& time) { - static std::optional prev_wpt; - static std::optional curr_wpt; int time_diff; // Start search at the beginning of the track @@ -877,7 +833,7 @@ static double interpolate_alt(const route_head* track, const gpsbabel::DateTime& * Pressure altitude and GNSS altitude may be provided in two separate * tracks. This function attempts to merge them into one. */ -static void wr_track() +void IgcFormat::wr_track() { const route_head* pres_track; const route_head* gnss_track; @@ -901,8 +857,9 @@ static void wr_track() printf(MYNAME ": adjusting time by %ds\n", time_adj); } // Iterate through waypoints in both tracks simultaneously + Interpolater interpolater; foreach (const Waypoint* wpt, gnss_track->waypoint_list) { - double pres_alt = interpolate_alt(pres_track, wpt->GetCreationTime().addSecs(time_adj)); + double pres_alt = interpolater.interpolate_alt(pres_track, wpt->GetCreationTime().addSecs(time_adj)); wr_fix_record(wpt, pres_alt, wpt->altitude); } } else { @@ -925,17 +882,17 @@ static void wr_track() } } -static void wr_init(const QString& fname) +void IgcFormat::wr_init(const QString& fname) { file_out = gbfopen(fname, "wb", MYNAME); } -static void wr_deinit() +void IgcFormat::wr_deinit() { gbfclose(file_out); } -static void data_write() +void IgcFormat::write() { gbfputs("AXXXZZZGPSBabel\r\n", file_out); wr_header(); @@ -944,26 +901,3 @@ static void data_write() gbfprintf(file_out, "LXXXGenerated by GPSBabel Version %s\r\n", gpsbabel_version); gbfputs("GGPSBabelSecurityRecordGuaranteedToFailVALIChecks\r\n", file_out); } - - -static QVector igc_args = { - { - "timeadj", &timeadj, - "(integer sec or 'auto') Barograph to GPS time diff", - nullptr, ARGTYPE_STRING, ARG_NOMINMAX, nullptr - }, -}; - -ff_vecs_t igc_vecs = { - ff_type_file, - { ff_cap_none , (ff_cap)(ff_cap_read | ff_cap_write), (ff_cap)(ff_cap_read | ff_cap_write) }, - rd_init, - wr_init, - rd_deinit, - wr_deinit, - data_read, - data_write, - nullptr, - &igc_args, - NULL_POS_OPS -}; diff --git a/igc.h b/igc.h new file mode 100644 index 000000000..4b1a0c49b --- /dev/null +++ b/igc.h @@ -0,0 +1,164 @@ +/* + * FAI/IGC data format translation. + * + * Refer to Appendix 1 of + * http://www.fai.org:81/gliding/gnss/tech_spec_gnss.asp for the + * specification of the IGC data format. This translation code was + * written when the latest amendment list for the specification was AL6. + * + * Copyright (C) 2004 Chris Jones + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +#ifndef IGC_H_INCLUDED_ +#define IGC_H_INCLUDED_ + +#include // for optional + +#include // for QByteArray +#include // for QDateTime +#include // for QList<>::const_iterator +#include // for QString, operator+, QStringLiteral +#include // for QVector + +#include "defs.h" +#include "format.h" +#include "gbfile.h" // for gbfprintf, gbfclose, gbfopen, gbfputs, gbfgetstr, gbfile +#include "src/core/datetime.h" // for DateTime + + +class IgcFormat : public Format +{ +public: + QVector* get_args() override + { + return &igc_args; + } + + ff_type get_type() const override + { + return ff_type_file; + } + + QVector get_cap() const override + { + return { ff_cap_none, (ff_cap)(ff_cap_read | ff_cap_write), (ff_cap)(ff_cap_read | ff_cap_write) }; + } + + void rd_init(const QString& fname) override; + void read() override; + void rd_deinit() override; + void wr_init(const QString& fname) override; + void write() override; + void wr_deinit() override; + +private: + + /* Types */ + + /* + * IGC record types. + * These appear as the first char in each record. + */ + enum igc_rec_type_t { + rec_manuf_id = 'A', // FR manufacturer and identification + rec_fix = 'B', // Fix + rec_task = 'C', // Task/declaration + rec_diff_gps = 'D', // Differential GPS + rec_event = 'E', // Event + rec_constel = 'F', // Constellation + rec_security = 'G', // Security + rec_header = 'H', // File header + rec_fix_defn = 'I', // List of extension data included at end of each fix (B) record + rec_extn_defn = 'J', // List of data included in each extension (K) record + rec_extn_data = 'K', // Extension data + rec_log_book = 'L', // Logbook/comments + + // M..Z are spare + + rec_none = 0, // No record + rec_bad = 1, // Bad record + }; + + class TaskRecordReader + { + public: + void igc_task_rec(const char*); + + private: + enum class state_t { id, takeoff, start, turnpoint, finish, landing }; + + unsigned int num_tp{0}; + unsigned int tp_ct{0}; + route_head* rte_head{nullptr}; + QDateTime creation; + state_t state{state_t::id}; + }; + + class Interpolater + { + public: + double interpolate_alt(const route_head*, const gpsbabel::DateTime&); + + private: + std::optional prev_wpt; + std::optional curr_wpt; + }; + + /* Constants */ + + static constexpr int kMaxRecLen = 79; // Includes null terminator and CR/LF + static constexpr int kMaxDescLen = 1024; + static constexpr char kPresTrkName[] = "PRESALTTRK"; + static constexpr char kGNSSTrkName[] = "GNSSALTTRK"; + + /* Member Functions */ + + static unsigned char coords_match(double, double, double, double); + igc_rec_type_t get_record(char**); + void detect_pres_track(const route_head*); + void detect_gnss_track(const route_head*); + void detect_other_track(const route_head*, int& max_waypt_ct); + void get_tracks(const route_head**, const route_head**); + QByteArray latlon2str(const Waypoint*); + QByteArray date2str(const gpsbabel::DateTime&) const; + QByteArray tod2str(const gpsbabel::DateTime&) const; + void wr_header(); + void wr_task_wpt_name(const Waypoint*, const char*); + void wr_task_hdr(const route_head*, unsigned int task_num); + void wr_task_wpt(const Waypoint*); + void wr_task_tlr(const route_head*); + void wr_tasks(); + void wr_fix_record(const Waypoint*, int, int); + int correlate_tracks(const route_head*, const route_head*) const; + void wr_track(); + + /* Data Members */ + + gbfile* file_in{}; + gbfile* file_out{}; + char manufacturer[4] {}; + const route_head* head{}; + char* timeadj = nullptr; + + QVector igc_args = { + { + "timeadj", &timeadj, + "(integer sec or 'auto') Barograph to GPS time diff", + nullptr, ARGTYPE_STRING, ARG_NOMINMAX, nullptr + } + }; +}; +#endif // IGC_H_INCLUDED_ diff --git a/vecs.cc b/vecs.cc index 45f0c1e90..e49c7ba7b 100644 --- a/vecs.cc +++ b/vecs.cc @@ -51,6 +51,7 @@ #include "gtrnctr.h" // for GtrnctrFormat #include "html.h" // for HtmlFormat #include "humminbird.h" // for HumminbirdFormat, HumminbirdHTFormat +#include "igc.h" // for IgcFormat #include "inifile.h" // for inifile_readstr #include "kml.h" // for KmlFormat #include "legacyformat.h" // for LegacyFormat @@ -77,7 +78,6 @@ extern ff_vecs_t tpg_vecs; extern ff_vecs_t tpo2_vecs; extern ff_vecs_t tpo3_vecs; extern ff_vecs_t gpl_vecs; -extern ff_vecs_t igc_vecs; extern ff_vecs_t brauniger_iq_vecs; extern ff_vecs_t mtk_vecs; extern ff_vecs_t mtk_fvecs; @@ -133,7 +133,7 @@ struct Vecs::Impl { #endif TextFormat text_fmt; HtmlFormat html_fmt; - LegacyFormat igc_fmt {igc_vecs}; + IgcFormat igc_fmt; LegacyFormat brauniger_iq_fmt {brauniger_iq_vecs}; LegacyFormat mtk_fmt {mtk_vecs}; LegacyFormat mtk_ffmt {mtk_fvecs}; -- 2.30.2